/* Custom USB HID Communication Device
 * By Omar Zabala.
 *
 * ***NOT FUNCTIONAL***
 * This class is not yet functional.
 * 
 * Licensed under the Apache 2.0 open source license
 */

using System;
using Microsoft.SPOT;
using System.Threading;
using Microsoft.SPOT.Hardware.UsbClient;

namespace Netduino_USB_HID_Library
{
    public static class Mouse
    {
        const int WRITE_ENDPOINT = 3;
        const int MAXPOWER_MILLIAMPS = 200;
        const int HID_DESCRIPTOR_TYPE = 0x21;

        private static int _x = 0;
        private static int _y = 0;

        public static bool started = false;
        private static UsbStream stream;
        public static UsbController usbController;

        public static string SetUp()
        {
            UsbController[] usbControllers = UsbController.GetControllers();

            if (usbControllers.Length < 1)
                return "Error: No controllers found.";

            usbController = usbControllers[0];

            try
            {
                Configure(usbController);

                if (!usbController.Start())
                    return "Error: Could not start controller.";

                stream = usbController.CreateUsbStream(WRITE_ENDPOINT, UsbStream.NullEndpoint);
            }
            catch (Exception e)
            { return "Error:\n" + e.Message; }

            return "Success";
        }

        public static void Configure(UsbController usbController)
        {
            Configuration.DeviceDescriptor deviceDescriptor = new Configuration.DeviceDescriptor(0x22B1, 0xFFFF, 0x0100); // VID, PID, version -- if you change the PID (0xFFFF) be sure to change the VID.
            Configuration configuration = new Configuration();
            Configuration.ConfigurationDescriptor cfgDesc = null;
            int cfgDescIndex = 0;
            int mouseInterfaceIndex = 0;

            // Create the device descriptor.
            deviceDescriptor = new Configuration.DeviceDescriptor(0xBADA, 0x0026,
                0x0100);
            deviceDescriptor.bcdUSB = 0x110;
            deviceDescriptor.bDeviceClass = 0;
            deviceDescriptor.bDeviceSubClass = 0;
            deviceDescriptor.bDeviceProtocol = 0;
            deviceDescriptor.bMaxPacketSize0 = 8;
            deviceDescriptor.iManufacturer = 1; // String #1 is the manufacturer name.
            deviceDescriptor.iProduct = 2;      // String #2 is the product name.
            deviceDescriptor.iSerialNumber = 0;

            Configuration.Endpoint endpoint = new Configuration.Endpoint(
                        3, Configuration.Endpoint.ATTRIB_Interrupt |
                        Configuration.Endpoint.ATTRIB_Write);
                    endpoint.wMaxPacketSize = 8; // Mouse data requires only eight bytes.
                    endpoint.bInterval = 10;     // Host should request data from mouse every 10 mS.

                    Configuration.Endpoint[] mouseEndpoints =
                        new Configuration.Endpoint[1] { endpoint };

                    // Set up the mouse interface.
                    Configuration.UsbInterface usbInterface =
                        new Configuration.UsbInterface(1, mouseEndpoints);
                    usbInterface.bInterfaceClass = 3;    // HID interface.
                    usbInterface.bInterfaceSubClass = 1; // Boot device.
                    usbInterface.bInterfaceProtocol = 2; // Standard mouse protocol.
                    usbInterface.iInterface = 1;    // Interface index.

                    // Assemble the HID class descriptor.
                    byte[] HidPayload = new byte[]
                    {
                        0x01, 0x01,     // bcdHID = HID version 1.01.
                        0x00,           // bCountryCode (unimportant).
                        0x01,           // bNumDescriptors = number of descriptors available for this device.
                        0x22,           // bDescriptorType = Report descriptor.
                        0x32, 0x00      // Total size of Report descriptor (50).
                    };

                    usbInterface.classDescriptors =
                        new Configuration.ClassDescriptor[1] { 
                            new Configuration.ClassDescriptor(0x21, HidPayload) };


                    Configuration.UsbInterface[] UsbInterfaces =
                                   new Configuration.UsbInterface[1] { usbInterface };
                    cfgDesc = new Configuration.ConfigurationDescriptor(
                        280, UsbInterfaces);
                    mouseInterfaceIndex = 0;
                    
                    // Create the report descriptor as a Generic.  This data was 
                    // created using...
                    byte[] BootMouseReportPayload = null;

                    BootMouseReportPayload = new byte[]
                    {
                        0x05, 0x01,  // USAGE_PAGE (Generic Desktop)
                        0x09, 0x02,  // USAGE (Mouse)
                        0xa1, 0x01,  // COLLECTION (Application)
                        0x09, 0x01,  //   USAGE (Pointer)
                        0xa1, 0x00,  //   COLLECTION (Physical)
                        0x05, 0x09,  //     USAGE_PAGE (Button)
                        0x19, 0x01,  //     USAGE_MINIMUM (Button 1)
                        0x29, 0x03,  //     USAGE_MAXIMUM (Button 3)
                        0x15, 0x00,  //     LOGICAL_MINIMUM (0)
                        0x25, 0x01,  //     LOGICAL_MAXIMUM (1)
                        0x95, 0x03,  //     REPORT_COUNT (3)
                        0x75, 0x01,  //     REPORT_SIZE (1)
                        0x81, 0x02,  //     INPUT (Data,Var,Abs)
                        0x95, 0x01,  //     REPORT_COUNT (1)
                        0x75, 0x05,  //     REPORT_SIZE (5)
                        0x81, 0x01,  //     INPUT (Cnst,Ary,Abs)
                        0x05, 0x01,  //     USAGE_PAGE (Generic Desktop)
                        0x09, 0x30,  //     USAGE (X)
                        0x09, 0x31,  //     USAGE (Y)
                        0x15, 0x81,  //     LOGICAL_MINIMUM (-127)
                        0x25, 0x7f,  //     LOGICAL_MAXIMUM (127)
                        0x75, 0x08,  //     REPORT_SIZE (8)
                        0x95, 0x02,  //     REPORT_COUNT (2)
                        0x81, 0x06,  //     INPUT (Data,Var,Rel)
                        0xc0,        //   END_COLLECTION
                        0xc0         // END_COLLECTION
                    };

                    const byte DescriptorRequest = 0x81;
                    const byte ReportDescriptor = 0x22;
                    const byte GetDescriptor = 0x06;
                    const ushort Report_wValue = (ushort)ReportDescriptor << 8;
                    Configuration.GenericDescriptor mouseReportDescriptor =
                        new Configuration.GenericDescriptor(DescriptorRequest,
                            Report_wValue, BootMouseReportPayload);
                    mouseReportDescriptor.bRequest = GetDescriptor;
                    mouseReportDescriptor.wIndex = 0;  // The interface number.
            
                    // Create the standard strings for the USB device
                    Configuration.StringDescriptor manufacturerNameStringDescriptor = new Configuration.StringDescriptor(1, "Secet Labs LLC");
                    Configuration.StringDescriptor productNameStringDescriptor = new Configuration.StringDescriptor(2, "Netduino Mouse");
                    Configuration.StringDescriptor displayNameStringDescriptor = new Configuration.StringDescriptor(4, "Netduino Mouse");
                    Configuration.StringDescriptor friendlyNameStringDescriptor = new Configuration.StringDescriptor(5, "Netduino Mouse");

                    configuration.descriptors =
                                    new Configuration.Descriptor[]
                        {
                            deviceDescriptor,
                            cfgDesc,
                            manufacturerNameStringDescriptor,
                            productNameStringDescriptor,
                            displayNameStringDescriptor,
                            friendlyNameStringDescriptor,
                            mouseReportDescriptor
                        };

                usbController.Configuration = configuration;
        }

        private static void Update()
        {
            stream.Write(new byte[] {0x00, (byte)_x, (byte)_y }, 0, 3);
            Thread.Sleep(20);
        }

        public static void MoveTo(int x, int y)
        {
            _x = x;
            _y = y;
            Update();
        }

        public static void Move(int xOff, int yOff)
        {
            _x += xOff;
            _y += yOff;
            Update();
        }
    }
}